<?php
/**
 * In this file the class '\UF\Drawing\Rectangle' is defined.
 *
 * @category   UniKap-Framework
 * @package    Drawing
 * @subpackage Core
 * @author     Ulf -UniKado- Kadner <ulfikado@gmail.com>
 * @since      2014-04-04 16:17
 * @version    0.1
 */

namespace UF\Drawing
{

    /**
     * A class that can represent a two-dimensional square, related to a location
     *
     * @property-read int $Right  The position of the right outer corner, in relation to the position zero point.
     * @property-read int $Bottom The position of the bottom outer corner, in relation to the position zero point.
     * @property-read int $Left   Alias for 'X'
     * @property-read int $X      The top left X coordinate
     * @property-read int $Top    Alias for 'Y'
     * @property-read int $Y      The top left Y coordinate.
     * @property-read int $Width  The width
     * @property-read int $Height The height
     * @since  v0.1
     */
    class Rectangle implements \UF\IStringConverter, \Serializable, \UF\IEquatable
    {

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   F I E L D S   - - - - - - - - - - - - - - - - - - - - - -">

        /**
         * The rectangle position.
         *
         * @var    \UF\Drawing\Point
         * @since  v0.1
         */
        public $Point;

        /**
         * The rectangle size.
         *
         * @var    \UF\Drawing\Size
         * @since  v0.1
         */
        public $Size;

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - >   C O N S T R U C T O R   - - - - - - - - - - - - - - - - - - - - - - - -">

        /**
         * Inits a new instance.
         *
         * @param \UF\Drawing\Point $point
         * @param \UF\Drawing\Size  $size
         * @since  v0.1
         */
        public function __construct( \UF\Drawing\Point $point = null, \UF\Drawing\Size $size = null )
        {
            if ( !\is_null( $point ) ) $this->Point = $point;
            else $this->Point = new \UF\Drawing\Point();
            if ( !\is_null( $size  ) ) $this->Size  = $size;
            else $this->Size  = new \UF\Drawing\Size();
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - -">

        /**
         * Inits a new Rectangle from the defined data.
         *
         * @param  int $x      The X coordinate.
         * @param  int $y      The Y coordinate.
         * @param  int $width  The width
         * @param  int $height The height
         * @return \UF\Drawing\Rectangle
         * @since  v0.1
         */
        public static function Init( $x, $y, $width, $height )
        {
            return new \UF\Drawing\Rectangle( new \UF\Drawing\Point( $x, $y ), new \UF\Drawing\Size($width, $height) );
        }

        /**
         * Inits a new Rectangle from the defined image resource.
         *
         * @param resource $imageResource
         * @return \UF\Drawing\Rectangle
         * @since  v0.1
         */
        public static function InitFromImageResource( $imageResource )
        {
            return new \UF\Drawing\Rectangle( new \UF\Drawing\Point(),
                new \UF\Drawing\Size( \imagesx( $imageResource ), \imagesy( $imageResource ) ) );
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   G E T T E R S   - - - - - - - - - - - - - - - - - - - - - - - - - - - -">

        /**
         * Returns the position of the right edge of the rectangle, relative to the zero of the current position.
         *
         * @return int
         * @since  v0.1
         */
        public final function getRight()
        {
            return $this->Point->X + $this->Size->Width;
        }

        /**
         * Returns the position of the bottom edge of the rectangle, relative to the zero of the current position.
         *
         * @return int
         * @since  v0.1
         */
        public final function getBottom()
        {
            return $this->Point->Y + $this->Size->Height;
        }

        /**
         * Returns a clone of the current instance.
         *
         * @return \UF\Drawing\Rectangle
         * @since  v0.1
         */
        public function getClone()
        {
            return new \UF\Drawing\Rectangle(
                new \UF\Drawing\Point(
                    $this->Point->X, $this->Point->Y ),
                new \UF\Drawing\Size(
                    $this->Size->Width, $this->Size->Height ) );
	}

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   M E T H O D S   - - - - - - - - - - - - - - - - - - - - -">

        # <editor-fold defaultstate="collapsed" desc="Some checking methods">

        /**
         * Gives back, whether this submitted Rectangle is contained completely in the current one.
         *
         * @param \UF\Drawing\Rectangle $rect
         * @return boolean
         * @since  v0.1
         */
        public final function contains( \UF\Drawing\Rectangle $rect )
        {
            return ( ( (
                ( $this->Point->X <= $rect->Point->X ) &&
                ( ( $rect->Point->X + $rect->Size->Width ) <= ( $this->Point->X + $this->Size->Width ) ) ) &&
                ( $this->Point->Y <= $rect->Point->Y ) ) &&
                ( ( $rect->Point->Y + $rect->Size->Height ) <= ( $this->Point->Y + $this->Size->Height ) ) );
        }

        /**
         * Returns, whether the indicated coordinates are within the current rectangle.
         *
         * @param  int $x The X coordinate
         * @param  int $y The Y coordinate
         * @return boolean
         * @since  v0.1
         */
        public final function containsLocation( $x, $y )
        {
            return ( ( (
                ( $this->Point->X <= $x ) &&
                ( $x < ($this->Point->X + $this->Size->Width ) ) ) &&
                ( $this->Point->Y <= $y ) ) &&
                ( $y < ($this->Point->Y + $this->Size->Height) ) );
        }

        /**
         * Gives back, whether the indicated point is within the current Rectangle.
         *
         * @param \UF\Drawing\Point $point The Point to check
         * @return boolean
         * @since  v0.1
         */
        public final function containsPoint( \UF\Drawing\Point $point )
        {
            return $this->containsLocation( $point->X, $point->Y );
        }

        /**
         * Returns, whether the indicated size is within the current rectangle size.
         *
         * @param \UF\Drawing\Size $size THe size to check
         * @return type
         * @since  v0.1
         */
        public final function containsSize( \UF\Drawing\Size $size )
        {
            return $this->Size->Width  >= $size->Width &&
    		   $this->Size->Height >= $size->Height;
        }

        /**
         * Returns if a empty Rectangle is defined.
         *
         * @return boolean
         * @since  v0.1
         */
        public final function isEmpty()
        {
            return $this->Point->isEmpty() && $this->Size->Width < 1 && $this->Size->Height < 1;
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="Some 'magic' overriding stuff">

        /**
         * Makes a Rectangle cloneable.
         *
         * @return \UF\Drawing\Rectangle
         * @since  v0.1
         */
        public function __clone()
        {
            return new \UF\Drawing\Size( clone $this->Point, clone $this->Size );
        }

        /**
         * Overrides the dynamic getter to make some dynamic fields accessible for reading.
         *
         * @param  string $name
         * @return int Or boolean FALSE
         * @since  v0.1
         */
        public function  __get( $name )
        {
            switch ( \strtolower( $name ) )
            {
                case 'left':   case 'x': return $this->Point->X;
                case 'top':    case 'y': return $this->Point->Y;
                case 'width':  case 'w': return $this->Size->Width;
                case 'height': case 'h': return $this->Size->Height;
                case 'bottom':           return $this->getBottom();
                case 'right':            return $this->getRight();
                default:                 return false;
            }
        }

        /**
         * Returns the corresponding string of thecurrent instance. The returned string use a format thats
         * usable with {@see \UF\Drawing\Rectangle::fromString()}!
         *
         * The returned string uses the following format:
         *
         * <code>x=0; y=0; width=800; height=600</code>
         *
         * @return string
         */
        public function __toString()
        {
            return (string)$this->Point . '; ' . (string)$this->Size;
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="Inflates + Intersects + Unions">

        /**
         * Enlarge or minimize the Rectangle by the indicated width and height on all sides and also
         * move the X and Y coordinates.
         *
         * The width as height is used, if no height is defined.
         *
         * @param int $width  The horizontal change to the left and to the right.
         * @param int $height The vertical change to the top and to the bottom.
         * @since  v0.1
         */
        public final function inflate( $width, $height=null )
        {
            if ( \is_null( $height ) ) $height = $width;
            $this->Point->X -= $width;
            $this->Point->Y -= $height;
            $this->Size->Width += ( 2 * $width );
            $this->Size->Height += ( 2 * $height );
	}

        /**
         * Enlarge or minimize the Rectangle by the indicated width and height on all sides and also
         * move the X and Y coordinates and returns the result as a new instance.
         *
         * The width as height is used, if no height is defined.
         *
         * @param int $width  The horizontal change to the left and to the right.
         * @param int $height The vertical change to the top and to the bottom.
         * @return \UF\Drawing\Rectangle
         * @since  v0.1
         */
        public final function inflateClone( $width, $height = null )
        {
            $clone = $this->getClone();
            $clone->inflate( $width, $height );
            return $clone;
	}

        /**
         * Enlarge or minimize the Rectangle by the indicated width and height on all sides BUT DOES NOT
         * MOVE THE X AND Y COORDINATES.
         *
         * The width as height is used, if no height is defined.
         *
         * @param int $width  The horizontal change to the left and to the right.
         * @param int $height The vertical change to the top and to the bottom.
         * @since  v0.1
         */
        public final function inflateFixed( $width, $height = null )
        {
            if ( \is_null( $height ) ) $height = $width;
            $this->Size->Width += (2 * $width);
            $this->Size->Height += (2 * $height);
	}

        /**
         * Enlarge or minimize the Rectangle by the indicated width and height on all sides BUT DOES NOT
         * MOVE THE X AND Y COORDINATES and returns the result as a new instance.
         *
         * The width as height is used, if no height is defined.
         *
         * @param int $width  The horizontal change to the left and to the right.
         * @param int $height The vertical change to the top and to the bottom.
         * @return \UF\Drawing\Rectangle
         * @since  v0.1
         */
        public final function inflateFixedClone( $width, $height = null )
        {
            $clone = $this->getClone();
            $clone->inflateFixed( $width, $height );
            return $clone;
        }

        /**
         * The intersection between the current Rectangle and the current rectangle is educational submitted and is
         * returned as a new \UF\Drawing\Rectangle.
         *
         * @param \UF\Drawing\Rectangle $rect
         * @return \UF\Drawing\Rectangle
         * @since  v0.1
         */
        public final function intersectClone( \UF\Drawing\Rectangle $rect )
        {
            $clone = $this->getClone();
            $clone->intersect( $rect );
            return $clone;
        }

        /**
         * The intersection between the current Rectangle and the current rectangle is educational submitted.
         *
         * @param \UF\Drawing\Rectangle $rect
         * @since  v0.1
         */
        public final function intersect( \UF\Drawing\Rectangle $rect )
        {
            $x = \max( $this->Point->X, $rect->Point->X );
            $num2 = \min( ( $this->Point->X + $this->Size->Width ), ( $rect->Point->X + $rect->Size->Width ) );
            $y = \max( $this->Point->Y, $rect->Point->Y );
            $num4 = \min( ( $this->Point->Y + $this->Size->Height ), ( $rect->Point->Y + $rect->Size->Height ) );
            if ( ( $num2 >= $x ) && ( $num4 >= $y ) )
            {
                $this->Size->Width = $num2 - $x;
                $this->Size->Height = $num4 - $y;
                $this->Point->X = $x;
                $this->Point->Y = $y;
            }
            else
            {
                $this->Size->Width = 0;
                $this->Size->Height = 0;
                $this->Point->X = 0;
                $this->Point->Y = 0;
            }
        }

        /**
         * Creates a new Rectangle, that contains the current Rectangle unioned with the submitted Rectangle and
         * returns it as a new \UF\Drawing\Rectangle instance.
         *
         * @param  \UF\Drawing\Rectangle $rect The Rectancle to union with
         * @return \UF\Drawing\Rectangle
         * @since  v0.1
         */
        public final function unionClone( \UF\Drawing\Rectangle $rect )
        {
            $clone = $this->getClone();
            $clone->union($rect);
            return $clone;
        }

        /**
         * Creates a new Rectangle, that contains the current Rectangle unioned with the submitted Rectangle.
         *
         * @param \UF\Drawing\Rectangle $rect The Rectancle to union with
         * @since  v0.1
         */
        public final function union( \UF\Drawing\Rectangle $rect )
        {
            $x = \max( $this->Point->X, $rect->Point->X );
            $num2 = \max( $this->Point->X + $this->Size->Width, $rect->Point->X + $rect->Size->Width );
            $y = \min( $this->Point->Y, $rect->Point->Y );
            $num4 = \max( $this->Point->Y + $this->Size->Height, $rect->Point->Y + $rect->Size->Height );
            $this->Size->Width = $num2 - $x;
            $this->Size->Height = $num4 - $y;
            $this->Point->X = $x;
            $this->Point->Y = $y;
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="Implement \UF\IStringConverter">

        /**
         * Returns the corresponding string of thecurrent instance. The returned string use a format thats
         * usable with {@see \UF\Drawing\Rectangle::fromString()}!
         *
         * The returned string uses the following format:
         *
         * <code>x=0; y=0; width=800; height=600</code>
         *
         * @return string
         * @since  v0.1
         */
        public function toString()
        {
            return (string) $this;
        }

        /**
         * Reinits the current instance data from the defined object string.
         *
         * This method will wort with data in a format, generated by casting a Rectangle instance to a string, or by
         * using {\UF\Drawing\Ractangle::toString()}.
         *
         * Allowed string must use the following format:
         *
         * <code>x=0; y=0; width=800; height=600</code>
         *
         * @param  string $objectString String that defines the data of a Rectangle instance.
         * @return boolean
         * @since  v0.1
         */
        public function fromString( $objectString )
        {
            $hits = null;
            if ( !\preg_match( '~^x=(\d{1,4});\s*y=(\d{1,4});\s*width=(\d{1,4});\s*height=(\d{1,4})$~',
                               $objectString, $hits ) )
                return false;
            $this->Point->X     = \intval( $hits[1] );
            $this->Point->Y     = \intval( $hits[2] );
            $this->Size->Width  = \intval( $hits[3] );
            $this->Size->Height = \intval( $hits[4] );
            return true;
        }

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="Implement Serializable">

        /**
         * Returns the instance data, serialized to a string.
         *
         * @return string
         * @since  v0.1
         */
        public function serialize()
        {
            return \serialize( $this->toArray() );
        }

        /**
         * Constructs the object from serialized data.
         *
	 * @param  string $serialized The serialized string representation of the object.
         * @since  v0.1
         */
        public function unserialize( $serialized )
        {
            $this->fromArray( \unserialize( $serialized ) );
        }

        # </editor-fold>

        /**
         * Overwrites the data of the current instance with the Rectangle data, defined by the XML element.
         * It requires that the XML element have the attributes 'x', 'y', 'width' and 'height'
         *
         * @param  \SimpleXMLElement $element The XML-Element to read the data from
         * @return boolean
         * @since  v0.1
         */
        public function readXML( \SimpleXMLElement $element )
        {
            if ( isset( $element['x'] ) && isset( $element['y'] ) &&
                 isset( $element['width'] ) && isset( $element['height'] ) )
            {
                $this->Point = new \UF\Drawing\Point(
                    \intval( (string) $element['x'] ), \intval( (string) $element['y'] ) );
                $this->Size  = new \UF\Drawing\Size(
                    \intval( (string) $element['width'] ), \intval( (string) $element['height'] ) );
                return true;
            }
            return false;
        }

        /**
         * Write all data of the implementing instance as a XML element into the submitted XMLWriter.
         *
         * @param  \XMLWriter $w The XMLWRiter.
         * @param  string $elementName The name of the XML element to be written (Defaults to 'Rectangle')
         * @since  v0.1
         */
        public function writeXML( \XMLWriter $w, $elementName = 'Rectangle' )
        {
            $w->startElement( $elementName );
            $this->writeXMLAttributes( $w );
            $w->endElement();
        }

        /**
         * Write all data of the implementing instance as XML element attributes into the submitted XMLWriter.
         *
         * @param \XMLWriter $w The XMLWRiter to write the attributes to.
         * @since  v0.1
         */
        public function writeXMLAttributes( \XMLWriter $w )
        {
            $this->Point->writeXMLAttributes( $w );
            $this->Size->writeXMLAttributes( $w );
        }

        /**
         * Returns all data of current object instance as an associative array, keys are 'x', 'y', 'width', 'height'.
         *
         * @return array Associative Array with the keys 'x', 'y', 'width', 'height'
         * @since  v0.1
         */
        public function toArray()
        {
            return array(
                'x'      => $this->Point->X,
                'y'      => $this->Point->Y,
                'width'  => $this->Size->Width,
                'height' => $this->Size->Height );
        }

        /**
         * Reinits the current instance data from the defined array data. The array must contain values with the keys
         * 'x', 'y', 'width' and 'height'
         *
         * @param  array $objectData Associative Array with keys 'x', 'y', 'width' and 'height'
         * @return boolean
         * @since  v0.1
         */
        public function fromArray( array $objectData )
        {
            $objectData = \array_change_key_case( $objectData );
            if ( isset( $objectData['x'] )     && isset( $objectData['y'] ) &&
                 isset( $objectData['width'] ) && isset( $objectData['height'] ) )
            {
                $this->Point->X     = \intval( $objectData['x'] );
                $this->Point->Y     = \intval( $objectData['y'] );
                $this->Size->Width  = \intval( $objectData['width'] );
                $this->Size->Height = \intval( $objectData['height'] );
                return true;
            }
            return false;
        }

        # <editor-fold defaultstate="collapsed" desc="Implement \UF\IEquatable">

        /**
         * Equals the current instance with the defined $value.
         *
         * If the 2nd parameter is TRUE, $value can only be equal if its a instance of {@see \UF\Drawing\Rectangle}.
         * Otherwise also values are checked, that can be converted to a {@see \UF\Drawing\Rectangle}.
         *
         * At least also $value can be of type {@see \UF\Drawing\Point} or {@see \UF\Drawing\Size}.
         *
         * @param  mixed   $value  The value that will be equaled with the current instance.
         * @param  boolean $strict Allow only values of type {@see \UF\Drawing\Rectangle}? (Defaults to FALSE)
         * @return boolean
         */
        public function equals( $value, $strict = false )
        {
            if ( \is_null( $value ) ) return false;
            if ( $value instanceof \UF\Drawing\Rectangle )
                return $value->Size->equals( $this->Size, true ) && $value->Point->equals( $this->Point, true );
            if ( $strict ) return false;
            if ( $value instanceof \UF\Drawing\Size ) return $value->equals( $this->Size, true );
            if ( $value instanceof \UF\Drawing\Point ) return $value->equals( $this->Point, true );
            $rect = null;
            if ( ! self::TryParse( $value, $rect ) ) return false;
            return $rect->Size->equals( $this->Size, true ) && $rect->Point->equals( $this->Point, true );
        }

        # </editor-fold>

        # </editor-fold>

        # <editor-fold defaultstate="collapsed" desc="- - - -   P U B L I C   S T A T I C   M E T H O D S   - - - - - - - - - - - - - -">

        /**
         * Parses a value of type \UF\Drawing\Size, \UF\Drawing\Point, string, array, SimpleXMLElement or of a type
         * that can be converted to a string, to a {@see \UF\Drawing\Rectangle} instance and returns if it was
         * successfull. On success the new instance is returned by parameter $output.
         *
         * @param  mixed $value The value to parse
         * @param  \UF\Drawing\Rectangle $output Returns the new \UF\Drawing\Rectangle instance if method returns TRUE.
         * @return boolean TRUE on success, fals otherwise
         */
        public static function TryParse( $value, &$output )
        {
            $output = null;
            if ( \is_null( $value ) ) return false;
            if ( $value instanceof \UF\Drawing\Rectangle ) {
                $output = $value; return true; }
            if ( $value instanceof \UF\Drawing\Size ) {
                $output = new \UF\Drawing\Rectangle( null, $value ); return true; }
            if ( $value instanceof \UF\Drawing\Point ) {
                $output = new \UF\Drawing\Rectangle( $value ); return true; }
            if ( \is_string( $value ) )
            {
                $tmp = new \UF\Drawing\Rectangle();
                if ( !$tmp->fromString( $value ) ) return false;
                $output = $tmp;
                return true;
            }
            if ( \is_array( $value ) )
            {
                $tmp = new \UF\Drawing\Rectangle();
                if ( !$tmp->fromArray( $value ) ) return false;
                $output = $tmp;
                return true;
            }
            if ( $value instanceof \SimpleXMLElement )
            {
                $tmp = new \UF\Drawing\Rectangle();
                if ( !$tmp->readXML( $value ) ) return false;
                $output = $tmp;
                return true;
            }
            $type = new \UF\Type( $value );
            if ( $type->hasAssociatedString() )
            {
                $tmp = new \UF\Drawing\Rectangle();
                if ( !$tmp->fromString( $type->getStringValue() ) ) return false;
                $output = $tmp;
                return true;
            }
            return false;
        }

        # </editor-fold>

    }

}

